#include "Sphere.hpp"

#include "Algebre/Solveur.hpp"

#include <cmath>

// pour debugger uniquement
//#include <fstream>

using namespace std;

/**
 * Constructeur par défaut, crée une Sphere centrée à l'origine et de
 * rayon 1.
 */
Sphere::Sphere()
  : Forme( "Sphère" ), m_Centre(), m_Rayon( 1. )
{
  PseudoCentre = m_Centre;
  PseudoRayon = m_Rayon;
  MoteurResolution = new Solveur();

  VersLocal = Transformation( MatriceSO3(), -m_Centre, 1. / m_Rayon );
  VersGlobal = VersLocal.inverse();
}

/**
 * Constructeur initialisant le centre de la Sphere et son rayon. Un
 * rayon négatif est automatiquement transformé en valeur positive.
 *
 * @param pt centre de la Sphere.
 * @param r rayon de la Sphere.
 */
Sphere::Sphere(const Coord& pt, const double& r)
  : Forme( "Sphère" ), m_Centre( pt ), m_Rayon( r )
{
  PseudoCentre = m_Centre;
  PseudoRayon = m_Rayon;
  MoteurResolution = new Solveur();

  VersLocal = Transformation( MatriceSO3(), -m_Centre, 1. / m_Rayon );
  VersGlobal = VersLocal.inverse();
}

/**
 * Constructeur de copie, permet de dupliquer une Sphere.
 *
 * @param s objet à copier.
 */
Sphere::Sphere(const Sphere &s)
  : Forme( "Sphère", s.VersGlobal )
{
  m_Centre = s.m_Centre;
  m_Rayon = s.m_Rayon;
  PseudoCentre = m_Centre;
  PseudoRayon = m_Rayon;
  MoteurResolution = new Solveur();
}

/**
 * Le destructeur n'a rien de spécial à faire.
 */
Sphere::~Sphere()
{
  delete MoteurResolution;
}

/**
 * Permet de récupérer la valeur du rayon.
 *
 * @return une référence constante sur m_Rayon.
 */
const double& Sphere::rayon() const
{
  return m_Rayon;
}

/**
 * Permet de récupérer le centre de la Sphere.
 *
 * @return une référence constante sur m_Centre.
 */
const Coord& Sphere::centre() const
{
  return m_Centre;
}


Coord Sphere::trouveIntersection(Rayon const & ray) const
{
  Coord origine( VersLocal( ray.origine() ) );
  Vecteur dir( VersLocal( ray.direction() ) );

  double b( dir * origine );
  b *= 2.;
  double c( origine.r2() - 1. );

  MoteurResolution -> setParams( c, b, dir.norme() );

  vector< double > solutions;
  
  MoteurResolution -> resoud( solutions );
  
  vector< double >::const_iterator It, Begin( solutions.begin() ),
    End( solutions.end() );

  Coord local;

   for ( It = Begin; It != End; It++ )
    {
      if ( *It > 1e-7 )
	{
	  local = origine;
	  local += (*It) * dir;
	  local = VersGlobal( local );
	  return /*static_cast< Coord >*/( local );
	}
    }

  return NullePart;
}

VecteurU Sphere::normale(Coord const & P) const
{
  //ici on ne tient pas compte de la position de P
  return VecteurU( m_Centre, P );
}

bool Sphere::estDans(Coord const & P) const
{
  Vecteur OP( P, m_Centre );
  if (  OP.norme() - pow( m_Rayon, 2 ) < 1.e-14  ) return true;
  else return false;
}

bool Sphere::estSur(Coord const & P) const
{
  Vecteur OP( P, m_Centre );
  if (  abs( OP.norme() - pow( m_Rayon, 2 ) ) < 1.e-14  ) return true;
  else return false;
}

void Sphere::mapping(const Coord &intersection, int &i, int &j,
		     const int &frequence, const int &Hauteur,
		     const int &Largeur) const
{
  /*
    double u, v, t;
    double ac;
  
    // Calul de u et de v tels que : (x, y, z) = (r*sinu,r*cosu*sinv, r*cosu*cosv)
    u = -asin( intersection.x() / m_Rayon);	//u est dans [-pi/2;pi/2]
  if (cos (u) != 0.0)
  {
  ac = intersection.z() / (m_Rayon * cos (u));
  if (ac<-1) ac=-1;
  if (ac>1) ac=1;
  v = acos (ac);
  }
  else
  v = 0.0;
  
  if (intersection.y() > 0.0)
  v = - v;			
  //v est dans [0;2pi[
  v+=ValeurPi;
  //calcul du pixel(i,j) correspondant à (u,v)
  t = (double) frequence *(u / ValeurPi + 0.5);
  //i = (int) (Hauteur * (t - (int) t));
  i = (int) (Hauteur * t);
  
  t = 0.5 * (double) frequence *v / ValeurPi;
  //j = (int) (Largeur * (t - (int) t));
  j = (int) (Largeur * (t));
  */

  //log << "phi = " << phi << ", theta = " << theta << endl;

  Coord carte( intrinseque( intersection ) );

  if ( intersection == NullePart )
    {
      i = j = 0;
      return;
    }

  double phi( carte.x() / m_Rayon ), theta( carte.y() / m_Rayon );
  double t;

  t = double( frequence * Hauteur ) * ( theta / ValeurPi );
  j = static_cast< int >( floor( t ) );

  t = double( frequence * Largeur ) * phi / ( 2 * ValeurPi );
  i = static_cast< int >( floor( t ) );
   
  i = i % Largeur;
  j = j % Hauteur;

  //log << "i = " << i << ", j = " << j << endl;
  //log.close();
}

/**
 * Affiche m_Center et m_Rayon.
 */
ostream& Sphere::affiche(ostream& os) const
{
  os << "Forme : " << nom() << endl << "Paramètres :" << endl
     << "\tCentre : " << m_Centre << endl
     << "\tRayon  : " << m_Rayon << endl;

  return os;
}

/**
 * On renvoie \f$ ( r \, \phi, r \, \theta, 0 ) \f$ où \f$\phi\f$ est
 * dans l'intervalle \f$\left[ 0, 2\pi \right[\f$ et \f$\theta\f$ dans
 * \f$\left[ 0, \pi \right[\f$.
 */
Coord Sphere::intrinseque(const Coord& pt) const
{
  if ( ! estSur( pt ) )
    return NullePart;

  Coord local( VersLocal( pt ) );

  double phi, theta;

  theta = acos( local.z() / m_Rayon );

  if ( abs( local.y() ) < 1.e-7 && local.x() > 1.e-7 )
    phi = 0.;
  else if ( abs( local.y() ) < 1.e-7 && local.x() < -1.e-7 )
    phi = ValeurPi;
  else
    phi = acos( local.x() / ( m_Rayon * sin( theta ) ) );
  
  if ( local.y() < -1.e-7 )
    phi = 2. * ValeurPi - phi;

  return Coord( m_Rayon * phi, m_Rayon * theta, 0. );
}

Coord Sphere::intrinsequeReduite(const Coord& pt, const double& larg) const
{
  if ( ! estSur ( pt ) )
    return NullePart;

  Coord carte( intrinseque( pt ) );

  // Attention, theta varie seulement dans l'intervalle [0, pi[

  //carte.y( carte.y() * 2. );

  return carte *= ( larg / ValeurPi );
}
